// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "google/protobuf-expectation/descriptor-proto-printer.h"

#include <vector>

#include "glog/logging.h"
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf-expectation/stubs/join.h"

using std::string;
using std::vector;

using google::protobuf::DescriptorProto;
using google::protobuf::FieldDescriptor;
using google::protobuf::FieldDescriptorProto;
using google::protobuf::FileDescriptorProto;
using google::protobuf::TextFormat;

namespace google {
namespace protobuf_expectation {

namespace {

const int kLabelNameSize = 4;
const char* kLabelName[kLabelNameSize] = {
  "", "optional", "required", "repeated"
};

const int kTypeNameSize = 19;
const char* kTypeName[kTypeNameSize] = {
  "", "double", "float", "int64", "uint64", "int32", "fixed64", "fixed32",
  "bool", "string", "group", "message", "bytes", "uint32", "enum", "sfixed32",
  "sfixed64", "sint32", "sint64"
};

bool PrintFieldDescriptorProtoToString(
    const FieldDescriptorProto& field_descriptor,
    const string& indent,
    string* output) {
  string label_name;
  if (field_descriptor.has_label()) {
    const FieldDescriptorProto::Label label = field_descriptor.label();
    if (label > 0 && label < kLabelNameSize) {
      label_name = StrCat(kLabelName[field_descriptor.label()], " ");
    } else {
      return false;
    }
  }
  string type_name;
  if (field_descriptor.has_type()) {
    const FieldDescriptorProto::Type type = field_descriptor.type();
    if (type > 0 && type < kTypeNameSize) {
      if (type == FieldDescriptorProto::TYPE_GROUP) {
        return false;  // not implemented
      } else if (type == FieldDescriptorProto::TYPE_MESSAGE ||
                 type == FieldDescriptorProto::TYPE_ENUM) {
        type_name = StrCat(field_descriptor.type_name(), " ");
      } else {
        type_name = StrCat(kTypeName[field_descriptor.type()], " ");
      }
    } else {
      return false;
    }
  }
  StrAppend(output, indent, label_name, type_name, field_descriptor.name(),
            " = ", field_descriptor.number(), ";\n");
  return true;
}

bool PrintDescriptorProtoToString(
    const DescriptorProto& descriptor,
    const string& indent,
    string* output) {
  StrAppend(output, indent, "message ", descriptor.name(), " {\n");
  for (int i = 0; i < descriptor.field_size(); ++i) {
    if (!PrintFieldDescriptorProtoToString(descriptor.field(i), indent + "  ",
                                           output)) {
      return false;
    }
  }
  for (int i = 0; i < descriptor.nested_type_size(); ++i) {
    StrAppend(output, "\n");
    if (!PrintDescriptorProtoToString(descriptor.nested_type(i), indent + "  ",
                                      output)) {
      return false;
    }
  }
  StrAppend(output, indent, "}\n");
  return true;
}

}  // namespace

bool PrintFileDescriptorProtoToString(
    const FileDescriptorProto& file_descriptor,
    const string& generated_by,
    string* output) {
  CHECK_NOTNULL(output);
  output->clear();
  StrAppend(output, "// This proto file is generated by ", generated_by, ".\n");
  if (file_descriptor.dependency_size() > 0) {
    StrAppend(output, "\n");
    for (int i = 0; i < file_descriptor.dependency_size(); ++i) {
      StrAppend(output, "import \"", file_descriptor.dependency(i), "\";\n");
    }
  }
  if (file_descriptor.has_options()) {
    StrAppend(output, "\n");
    // Print all existing fields in the FileOptions.
    vector<const FieldDescriptor*> fields;
    file_descriptor.options().GetReflection()->
        ListFields(file_descriptor.options(), &fields);
    for (int i = 0; i < fields.size(); ++i) {
      if (fields[i]->is_repeated()) {
        continue;  // repeated field is not expected in FileOptions.
      }
      string field_value;
      TextFormat::PrintFieldValueToString(
          file_descriptor.options(), fields[i], -1, &field_value);
      StrAppend(output, "option ", fields[i]->name(), " = ", field_value,
                ";\n");
    }
  }
  if (file_descriptor.has_package()) {
    StrAppend(output, "\n");
    StrAppend(output, "package ", file_descriptor.package(), ";\n");
  }
  for (int i = 0; i < file_descriptor.message_type_size(); ++i) {
    StrAppend(output, "\n");
    if (!PrintDescriptorProtoToString(file_descriptor.message_type(i), "",
                                      output)) {
      return false;
    }
  }

  return true;
}

}  // namespace protobuf_expectation
}  // namespace google
